package com.qdlc.p2p.dal.mybatis.pager.core;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
import java.util.StringTokenizer;

import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.RowBounds;

import com.qdlc.p2p.dal.mybatis.pager.support.DefaultPageSqlHandlerFactory;



/**
 * MyBatis分页处理的拦截器，该拦截器对StatementHandler的prepare方法进行拦截
 * 
 * 思路： 1. 在StatementHandler执行query方法前，会先处理prepare方法，在prepared中进行拦截，对sql进行处理，
 * 将原始的sql加上分页信息。
 * 2.参数对象为PageParameterWrapper类型时进行分析处理，如果不是PageParameterWrapper类型，则不进行分析处理 。
 * 3.从PageParameterWrapper中把包装的真正的参数对象重新设置到boundSql和parameterHandler中去。
 * 
 * @author linya 2015-06-09 19:50:49
 *
 */
@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class }) })
public class PageStatementInterceptor implements Interceptor {

	// 默认定义MetaObject使用时的ObjectFactory和ObjectWrapperFactory两个参数
	private ObjectFactory objectFactory = new DefaultObjectFactory();
	private ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();

	//从配置文件中获取方言，mysql | orcale | PageSqlHandler的实现类型，默认为mysql。
	String dialect = "mysql";
	
	//pageSqlHandler根据dialect来实例化的。
	private PageSqlHanlder pageSqlHandler;

	public Object intercept(Invocation invocation) throws Throwable {
		// 拦截的目标对象StatementHandler
		StatementHandler handler = (StatementHandler) invocation.getTarget();
		// 获取包装参数
		PageParameterWrapper parameterWrapper = PageInterceptorContext.getAndRemoveStatmentContext();
		if(parameterWrapper != null){
			Connection connection = (Connection) invocation.getArgs()[0];
			parsePageParameterWrapper(handler, parameterWrapper, connection);
		}
		return invocation.proceed();
	}
	
	/**
	 * 从StatementHandler中解析出parameterObject对象，对StatementHandler中的BoundSql进行重新设置，
	 * 将BoundSql中的sql和预定义参数修改成分页查询的sql和参数
	 * @param handler
	 * @param parameterWrapper
	 * @param connection
	 */
	private void parsePageParameterWrapper(StatementHandler handler, PageParameterWrapper parameterWrapper, Connection connection){
		// 分页参数对象
		PageParameter pagerParameter = parameterWrapper.getPageParameter();
		//BoundSql
		BoundSql boundSql = handler.getBoundSql();
		//执行SQL
		String sql = boundSql.getSql();
		// 将sql处理为分页后的sql语句
		String pageSql = buildPageSql(sql, pagerParameter);

		// 获取到handler的MetaObject对象, 循环处理代理对象，从代理对象中抽离出真实的对象
		MetaObject metaHandler = MetaObject.forObject(handler, objectFactory, objectWrapperFactory);
		while (metaHandler.hasGetter("h")) {
			Object proxy = metaHandler.getValue("h");
			metaHandler = MetaObject.forObject(proxy, objectFactory, objectWrapperFactory);
		}

		// 经过此处target的获取，metaHandler为拦截前的StatementHandler对象，即为RoutingStatementHandler实例对象
		while (metaHandler.hasGetter("target")) {
			Object target = metaHandler.getValue("target");
			metaHandler = MetaObject.forObject(target, objectFactory, objectWrapperFactory);
		}

		// 真正的StatementHandler实例
		metaHandler = MetaObject.forObject(metaHandler.getValue("delegate"), objectFactory, objectWrapperFactory);
		
		// 获取MappedStatement实例
		MappedStatement mappedStatement = (MappedStatement) metaHandler.getValue("mappedStatement");
		
		// 重置查询的Sql语句
		metaHandler.setValue("boundSql.sql", pageSql);

		// 采用物理分页后，就不需要mybatis的内存分页了，重置下面的两个参数
		metaHandler.setValue("rowBounds.offset", RowBounds.NO_ROW_OFFSET);
		metaHandler.setValue("rowBounds.limit", RowBounds.NO_ROW_LIMIT);

		// 获取分页参数里的总记录数
		setPageParameter(sql, connection, mappedStatement, boundSql, pagerParameter);
	}

	/**
	 * 查询总记录数，并将总数设置到PageParameter的totalCount中去
	 * 
	 * @author linya 2015-06-09 19:45:34
	 * @param sql
	 * @param connection
	 * @param mappedStatement
	 * @param boundSql
	 * @param pagerParameter
	 */
	private void setPageParameter(String sql, Connection connection, MappedStatement mappedStatement, BoundSql boundSql, PageParameter pagerParameter) {
		// 记录总记录数
		String countSql = getCountSql(sql);
		PreparedStatement countStmt = null;
		ResultSet rs = null;
		try {
			countStmt = connection.prepareStatement(countSql);
			BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(),
					countSql, boundSql.getParameterMappings(),
					boundSql.getParameterObject());
			ParameterHandler parameterHandler = new DefaultParameterHandler(
					mappedStatement, boundSql.getParameterObject(), countBS);
			parameterHandler.setParameters(countStmt);
			rs = countStmt.executeQuery();
			int totalCount = 0;
			if (rs.next()) {
				totalCount = rs.getInt(1);
			}
			pagerParameter.setTotalCount(totalCount);

		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			try {
				if(rs != null){
					rs.close();
				}
				if(countStmt != null){
					countStmt.close();
				}
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}

	}
	
	@Override
	public Object plugin(Object target) {
		if (target instanceof StatementHandler) {
			return Plugin.wrap(target, this);
		}
		return target;
	}

	@Override
	public void setProperties(Properties properties) {
	}
	
	/**
	 * 构建分页查询的sql
	 * @param sql
	 * @param pagerParameter
	 * @return
	 */
	private String buildPageSql(String sql, PageParameter pagerParameter) {
		if(pagerParameter == null){
			throw new IllegalArgumentException("pagerParameter can't null");
		}
		if(pageSqlHandler == null){
			initPageSqlHandler(dialect);
		}
		return pageSqlHandler.handle(sql, pagerParameter);
	}
	
	public void setDialect(String dialect) {
		this.dialect = dialect;
	}

	/**
	 * 工厂创建PageSqlHanlder实例
	 * @param dialect
	 */
	private void initPageSqlHandler(String dialect) {
		PageSqlHandlerFactory factory = new DefaultPageSqlHandlerFactory();
		pageSqlHandler = factory.factory(dialect);
	}

	private String getCountSql(String sql) {
		
		//分割sql，可能会有子语句存在
		StringTokenizer st = new StringTokenizer(sql, "( )\t\n");
		
		//查找第几个from和开始的select匹配
		int selectCount = 0;
		int fromCount = 0;
		while (st.hasMoreElements()) {
			String c = st.nextElement().toString();
			if("select".equalsIgnoreCase(c)){
				selectCount ++;
			}
			if("from".equalsIgnoreCase(c)){
				fromCount ++;
			}
			if(selectCount != 0 && (selectCount == fromCount)){
				break;
			}
        }
		
		//查找到from位置
		int index = - 1;
		String sqlTmp = sql.toLowerCase();
		while(fromCount > 0){
			index = sqlTmp.indexOf("from", index + 1);
			fromCount --;
		}
		
		//替换为统计语句
		String subSql = sql.substring(index + 4).trim();
		String countSql = "select count(0) from " + subSql;
		
		return countSql;
	}
}
